package cn.s1995.sloth.bizs.util;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.aliyun.imagesearch20201214.Client;
import com.aliyun.imagesearch20201214.models.*;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ImageSearchUtil {

	private static final double IMAGE_MAX_SIZE = 3.9 * 1024 * 1024;
	private static final double MAX_PX = 4096D;

	@Resource
	private Config aliImageSearchConfig;
	@Value("${imagesearch.instance_name}")
	private String instanceName;
	@Value("${imagesearch.num}")
	private Integer imagesearchNum;
	@Value("${imagesearch.min_score}")
	private Float minScore;
//	@Value("${imagesearch.success_file}")
//	private String successPath;
//	@Value("${imagesearch.error_file}")
//	private String errorPath;

	/**
	 * 添加图片到图片库
	 * @param productId
	 * @param picName
	 * @param categoryId
	 * @param inputStream
	 * @throws Exception
	 */
	@SentinelResource(value = "imageAdd")
	public void add(String productId, String picName, String categoryId, InputStream inputStream) throws Exception {
		Client client = new Client(aliImageSearchConfig);
		AddImageAdvanceRequest request = new AddImageAdvanceRequest();
		// 必填，图像搜索实例名称。注意是实例名称不是实例ID。
		request.setInstanceName(instanceName);
		// 必填，商品id，最多支持512个字符。
		request.setProductId(productId);
		// 必填，图片名称，最多支持512个字符。
		request.setPicName(picName);
		// 选填，图片类目。
//        request.setCategoryId(categoryId);
		// 选填，用户自定义的内容，最多支持4096个字符。
		// 查询时会返回该字段。例如可添加图片的描述等文本。
//        request.customContent = "this is a simple test";
		// 选填，整数类型属性，可用于查询时过滤，查询时会返回该字段。
		// 例如不同的站点的图片/不同用户的图片，可以设置不同的IntAttr，查询时通过过滤来达到隔离的目的
//        request.intAttr = 56;
		// 选填，字符串类型属性，最多支持128个字符。可用于查询时过滤，查询时会返回该字段。
//        request.strAttr = "test";
		if(StrUtil.isNotBlank(categoryId))
			request.setStrAttr(categoryId);
		// 选填，是否需要进行主体识别，默认为true。
		// 1.为true时，由系统进行主体识别，以识别的主体进行搜索，主体识别结果可在Response中获取。
		// 2.为false时，则不进行主体识别，以整张图进行搜索。
		request.setCrop(true);
		// 选填，图片的主体区域，格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点，x2，y2是右下角的点。设置的region 区域不要超过图片的边界。
		// 若用户设置了Region，则不论Crop参数为何值，都将以用户输入Region进行搜索。
		RuntimeOptions runtimeOptions = new RuntimeOptions();
		try {
			// 图片内容，最多支持 4MB大小图片以及5s的传输等待时间。当前仅支持PNG、JPG、JPEG、BMP、GIF、WEBP、TIFF、PPM格式图片；
			// 对于商品、商标、通用图片搜索，图片长和宽的像素必须都大于等于100且小于等于4096；
			// 对于布料搜索，图片长和宽的像素必须都大于等于448且小于等于4096；
			// 图像中不能带有旋转信息
			request.setPicContentObject(inputStream);
			AddImageResponse response = client.addImageAdvance(request,runtimeOptions);
			AddImageResponseBody body = response.getBody();
			if(body.getCode() != 0) {
//                RandomAccessFile errorRaf = new RandomAccessFile(errorPath, "rw");
//                errorRaf.seek(errorRaf.length());
//                errorRaf.write((productId + " " + picName).getBytes());
//                errorRaf.writeBytes("\r\n");
//                errorRaf.close();
				log.error("新增失败,{}", body.toString());
				throw new RuntimeException("新增失败");
			}
//            RandomAccessFile successRaf = new RandomAccessFile(successPath, "rw");
//            successRaf.seek(successRaf.length());
//			successRaf.write((productId + " " + picName).getBytes());
//			successRaf.writeBytes("\r\n");
//			successRaf.close();
		} catch (TeaException e) {
//            RandomAccessFile errorRaf = new RandomAccessFile(errorPath, "rw");
//            errorRaf.seek(errorRaf.length());
//            errorRaf.write((productId + " " + picName).getBytes());
//            errorRaf.writeBytes("\r\n");
//            errorRaf.close();
			log.error("code:{},data:{},message:{}", e.getCode(), e.getData(), e.getMessage());
			throw e;
		} finally {
			inputStream.close();
		}
	}

	/**
	 * 删除图片
	 * @param productId
	 * @param picName
	 * @throws Exception
	 */
	@SentinelResource(value = "imageDelete")
	public void delete(String productId, String picName) throws Exception {
		Client client = new Client(aliImageSearchConfig);
		DeleteImageRequest request = new DeleteImageRequest();
		// 必填，图像搜索实例名称。注意是实例名称不是实例ID。
		request.setInstanceName(instanceName);
		// 必填，商品id，最多支持512个字符。
		request.setProductId(productId);
		// 选填，图片名称。若不指定本参数，则删除ProductId下所有图片；若指定本参数，则删除ProductId+PicName指定的图片。
		if(StrUtil.isNotBlank(picName))
			request.setPicName(picName);
		try {
			DeleteImageResponse response = client.deleteImage(request);
			DeleteImageResponseBody body = response.getBody();
			if(body.getCode() != 0) {
				log.error("删除失败,{}", body.toString());
				throw new RuntimeException("删除失败");
			}
		} catch (TeaException e) {
			log.error("code:{},data:{},message:{}", e.getCode(), e.getData(), e.getMessage());
			throw e;
		}
	}

	/**
	 * 以图搜图
	 * @param inputStream
	 * @return
	 * @throws Exception
	 */
	@SentinelResource(value = "imageSearch")
	public List<String> searchByPic(InputStream inputStream) throws Exception {
		Client client = new Client(aliImageSearchConfig);
		SearchImageByPicAdvanceRequest request = new SearchImageByPicAdvanceRequest();
		// 必填，图像搜索实例名称。注意是实例名称不是实例ID。
		request.setInstanceName(instanceName);
		// 选填，商品类目。
		// 1. 对于商品搜索：若设置类目，则以设置的为准；若不设置类目，将由系统进行类目预测，预测的类目结果可在Response中获取 。
		// 2. 对于布料、商标、通用搜索：不论是否设置类目，系统会将类目设置为88888888。
//        request.categoryId = 3;
		// 选填，返回结果的数目。取值范围：1-100。默认值：10。
		request.setNum(imagesearchNum);
		// 选填，返回结果的起始位置。取值范围：0-499。默认值：0。
		request.setStart(0);
		// 选填，是否需要进行主体识别，默认为true。
		// 1.为true时，由系统进行主体识别，以识别的主体进行搜索，主体识别结果可在Response中获取。
		// 2.为false时，则不进行主体识别，以整张图进行搜索。
		request.setCrop(true);
		// 选填，图片的主体区域，格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点，x2，y2是右下角的点。设置的region 区域不要超过图片的边界。
		// 若用户设置了Region，则不论Crop参数为何值，都将以用户输入Region进行搜索。
//        request.region="167,467,221,407";
		// 选填，过滤条件
		// int_attr支持的操作符有>、>=、<、<=、=，str_attr支持的操作符有=和!=，多个条件之支持AND和OR进行连接。
		// 示例：
		//  1. 根据IntAttr过滤结果，int_attr>=100
		//  2. 根据StrAttr过滤结果，str_attr!="value1"
		//  3. 根据IntAttr和StrAttr联合过滤结果，int_attr=1000 AND str_attr="value1"
//        request.filter="int_attr=56 AND str_attr=\"test\"";
		// 图片内容，最多支持 4MB大小图片以及5s的传输等待时间。当前仅支持PNG、JPG、JPEG、BMP、GIF、WEBP、TIFF、PPM格式图片；
		// 对于商品、商标、通用图片搜索，图片长和宽的像素必须都大于等于100且小于等于4096；
		// 对于布料搜索，图片长和宽的像素必须都大于等于448且小于等于4096；
		// 图像中不能带有旋转信息
		RuntimeOptions runtimeObject =  new RuntimeOptions();
		try {
			request.setPicContentObject(inputStream);
//            log.info("请求阿里前" + LocalDateTime.now().toString());
			SearchImageByPicResponse response = client.searchImageByPicAdvance(request, runtimeObject);
			SearchImageByPicResponseBody body = response.getBody();
			if(body.getCode() != 0) {
				log.error("查询失败,{}", body.toString());
				throw new RuntimeException("查询失败");
			}
//            log.info("请求阿里后" + LocalDateTime.now().toString());
			List<SearchImageByPicResponseBody.SearchImageByPicResponseBodyAuctions> auctions = body.getAuctions();
			if(ObjectUtil.isNotEmpty(auctions)) {
				return auctions.stream()
						.filter(e -> e.getScore() > minScore)
						.map(e -> e.getProductId()).distinct().collect(Collectors.toList());
			}
		} catch (TeaException e) {
			log.error("code:{},data:{},message:{}", e.getCode(), e.getData(), e.getMessage());
			throw e;
		} finally {
			if(inputStream != null)
				inputStream.close();
		}
		return null;
	}

	/**
	 * 调整图片
	 * @param file
	 * @param suffix
	 * @return
	 * @throws IOException
	 */
	@SentinelResource(value = "imageAdjust")
	public InputStream adjust(MultipartFile file, String suffix) throws IOException {
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		InputStream inputStream = null;
		try{
			long size = file.getSize();
			double scale = 1;
			int width = bufferedImage.getWidth();
			int height = bufferedImage.getHeight();
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			if(size > IMAGE_MAX_SIZE) {
				scale = Math.min(0.7d, IMAGE_MAX_SIZE / size);
			}
			if( scale < 1) {
				inputStream = file.getInputStream();
				Thumbnails.of(inputStream).scale(scale).toOutputStream(bos);
			}else if(width > 4000 || height > 4000){
				inputStream = file.getInputStream();
				Thumbnails.of(inputStream).size(4000, 4000).toOutputStream(bos);
			}else {
				return file.getInputStream();
			}
			return new ByteArrayInputStream(bos.toByteArray());
		} finally {
			if(bufferedImage != null)
				bufferedImage.getGraphics().dispose();
			if(inputStream != null)
				inputStream.close();
		}
	}

	/**
	 * 压缩图片
	 * @param inputStream
	 * @param suffix
	 * @param size
	 * @return
	 * @throws IOException
	 */
	@Deprecated
	@SentinelResource(value = "imageAdjust")
	public InputStream adjust(InputStream inputStream, String suffix, long size) throws IOException {
		BufferedImage bufferedImage = ImageIO.read(inputStream);
		BufferedImage compressBufferedImage = null;
		try {
			double scale = 1;
			int width = bufferedImage.getWidth();
			int height = bufferedImage.getHeight();
			if(width > MAX_PX) {
				scale = Math.min(scale, MAX_PX / width);
			}
			if(height > MAX_PX) {
				scale = Math.min(scale, MAX_PX / height);
			}
			if(size > IMAGE_MAX_SIZE) {
				scale = Math.min(scale, IMAGE_MAX_SIZE / size);
			}
			if( scale < 1) {
				if("png".equals(suffix) && (width > 1500 || height > 1500)) {
					scale = Math.min( 1000d / Math.max(width, height), scale);
				}else {
					scale = Math.min(0.7d, scale);
				}
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				width = (int)(width * scale);
				height = (int)(height * scale);
				compressBufferedImage = new BufferedImage(width, height, bufferedImage.getType() == 0?5:bufferedImage.getType());
				compressBufferedImage.getGraphics().drawImage(bufferedImage, 0, 0, width, height, null);
				ImageIO.write(compressBufferedImage, suffix, bos);
				return new ByteArrayInputStream(bos.toByteArray());
			}else {
				return null;
			}
		} finally {
			if(compressBufferedImage != null)
				compressBufferedImage.getGraphics().dispose();
			if(bufferedImage != null)
				bufferedImage.getGraphics().dispose();
			if(inputStream != null)
				inputStream.close();
		}
	}

	/**
	 * 判断文件是否为图片
	 * @param suffix
	 * @return
	 */
	public static boolean isPicture(String suffix) {
		if (StrUtil.isBlank(suffix)) {
			return false;
		}
		suffix = suffix.toLowerCase();
		String[] arr = {"png", "jpg", "jpeg", "bmp", "gif", "webp", "tiff", "ppm"};
		for (String item : arr) {
			if (item.equals(suffix)) {
				return true;
			}
		}
		return false;
	}

}
